type Dictionary = Record<string, string>;

let language: string = globalThis.navigator?.language?.toLowerCase() ?? 'en';
let shortLanguage: string = language.split('-')[0];

/**
 * Set specified language as translate target
 * @param lang en, en-us or en-US, zh, zh-cn ...
 */
export function setLanguage(lang: string): void {
	if (lang) {
		language = lang.toLowerCase();
		shortLanguage = language.split('-')[0];
	}
}

/**
 * Get current language
 * @returns
 */
export function getLanguage(): string {
	return language;
}

const dictionaries = new Map<string, Dictionary>();

/**
 * Clear all dictionaries
 */
export function clearDictionaries(): void {
	dictionaries.clear();
}

/**
 * Get all dictionaries
 * @returns
 */
export function getDictionaries(): Record<string, Dictionary> {
	return Object.fromEntries(dictionaries.entries());
}

function getDictKeys<T extends Record<string, unknown>>(dictionary: T): { [key in keyof T]: string } {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	const obj: any = {};
	Object.defineProperties(
		obj,
		Object.fromEntries(
			Object.keys(dictionary).map((k) => [
				k,
				{
					get: () => i18n(k),
				},
			])
		)
	);
	return obj;
}

/**
 * Add a dictionary for languages or merge items to existing one.
 * @param lang Language array, if multiple languge use the same dictionary,
 * it should be joined by ',' and put in the same element of the array.
 * @param dictionary Dictionary object. If a key contains translations in multiple languages,
 * those translations should be placed in an array in the same order as the language array.
 */
export function addDictArray<T extends Record<string, string[]>>(
	lang: string[],
	dictionary: T
): { [key in keyof T]: string } {
	Object.keys(dictionary).forEach((k) => {
		lang.forEach((l, idx) => {
			l.split(',').forEach((l) => {
				addDict(l, { [k]: dictionary[k][idx] });
			});
		});
	});
	return getDictKeys(dictionary);
}

/**
 * Add a dictionary for specified language or replace existing one.
 * @param lang en-us, zh-cn ...
 * @param dictionary Language dictionary
 * @returns
 */
export function setDict<T extends Dictionary>(lang: string, dictionary: T): { [key in keyof T]: string } {
	if (typeof lang === 'string') lang = lang.toLowerCase();
	dictionaries.set(lang, dictionary);
	return getDictKeys(dictionary);
}

/**
 * Add a dictionary for specified language or merge items to existing one.
 * @param lang en-us, zh-cn ...
 * @param dictionary Language dictionary
 * @returns
 */
export function addDict<T extends Dictionary>(lang: string, dictionary: T): { [key in keyof T]: string } {
	if (typeof lang === 'string') lang = lang.toLowerCase();
	const dict = dictionaries.get(lang);
	if (!dict) {
		dictionaries.set(lang, dictionary);
	} else {
		Object.keys(dictionary).forEach((k) => {
			if (dictionary[k] != undefined) {
				dict[k] = dictionary[k];
			}
		});
	}
	return getDictKeys(dictionary);
}

function translate(str: unknown, dict: Dictionary | undefined): string {
	if (typeof str !== 'string') {
		return str + '';
	}
	if (!dict) {
		return str;
	}
	const v = dict[str];
	if (v) {
		return v;
	}
	return str;
}

/**
 * Translate a string to target language.
 * @param str String to be translated
 * @param dict Dictionary to be used, if not specified, the dictionary for current language will be used.
 * @returns
 */
export function i18n(str: unknown, dict?: Dictionary): string {
	const d: Dictionary | undefined =
		dict ?? dictionaries.get(language) ?? dictionaries.get(shortLanguage) ?? dictionaries.get('en');
	return translate(str, d);
}

/**
 * Tagged string template function used to translate english text to other languages.
 * @param strs Text template array
 * @param params Parameters
 */
export function T(strs: TemplateStringsArray, ...params: unknown[]): string {
	const dict = dictionaries.get(language) ?? dictionaries.get(shortLanguage) ?? dictionaries.get('en');
	let result = '';
	for (let i = 0; i < strs.length; i++) {
		result += translate(strs[i], dict);
		if (i < params.length) {
			result += params[i];
		}
	}
	return result;
}
